package com.zhouyer.study.mvcframework.servlet;

import com.zhouyer.study.mvcframework.annotations.*;
import com.zhouyer.study.mvcframework.pojo.Handler;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ZyDispatcherServlet extends HttpServlet {

    /**
     * 用于保存配置文件的属性值
     */
    private Properties properties = new Properties();

    /**
     * 缓存扫描到的类的全限定类名
     */
    private List<String> classNames = new ArrayList<>();

    /**
     * ioc容器，存放实例化的bean对象
     */
    private Map<String, Object> iocMap = new HashMap<>(16);

    /**
     * HandlerMapping 保存url与method的映射关系
     */
//    private Map<String, Handler> handlerMapping = new HashMap<>(16);
    private List<Handler> handlerMapping = new ArrayList<>(10);

    @Override
    public void init(ServletConfig config) throws ServletException {
        // 1、加载配置文件 springmvc.properties
        // 获取到初始化参数
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        doLoadConfig(contextConfigLocation);

        // 2、扫描相关的类，扫描注解
        doScan(properties.getProperty("scanPackage"));

        // 3、初始化Bean对象（基于注解实现IoC容器）
        doInstance();

        // 4、实现依赖注入
        doAutoWired();

        // 5、构造一个HandlerMapping处理器映射器，将配置好的url和method建立映射关系
        initHandlerMapping();

        System.out.println("zy mvc 初始化完成");
        // 等待请求进入，处理请求
    }

    /**
     * 构造一个HandlerMapping处理器映射器，将配置好的url和method建立映射关系
     */
    private void initHandlerMapping() {
        if (MapUtils.isEmpty(iocMap)) {
            return;
        }
        /**
         * 遍历ioc容器，如果有@ZyController的才能进行url与method映射关系
         */
        for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
            // 获取ioc容器中对象的Class类型
            Class<?> aClass = entry.getValue().getClass();
            // 如果没有@ZyController注解的直接跳过
            if (!aClass.isAnnotationPresent(ZyController.class)) {
                continue;
            }
            //记录有权限访问该handler的用户
            List<String> userListInClass = new ArrayList<>(10);
            if (aClass.isAnnotationPresent(Security.class)) {
                String[] value = aClass.getAnnotation(Security.class).value();
                if (ArrayUtils.isNotEmpty(value)) {
                    userListInClass.addAll(Arrays.asList(value));
                }
            }
            // 记录url（类上@ZyRequestMapping的值+handler方法上@ZyRequestMapping的值）
            String baseUrl = "";
            if (aClass.isAnnotationPresent(ZyRequestMapping.class)) {
                ZyRequestMapping annotation = aClass.getAnnotation(ZyRequestMapping.class);
                if (StringUtils.isNoneBlank(annotation.value())) {
                    baseUrl = annotation.value();
                }
            }
            // 获取当前对象的方法
            Method[] methods = aClass.getMethods();
            for (Method method : methods) {
                if (!method.isAnnotationPresent(ZyRequestMapping.class)) {
                    continue;
                }
                ZyRequestMapping annotation = method.getAnnotation(ZyRequestMapping.class);
                if (StringUtils.isNoneBlank(annotation.value())) {
                    String methodUrl = annotation.value();
                    String url = baseUrl + methodUrl;

                    // 将method所有信息及url封装到一个Handler中进行统一存储
                    Handler handler = new Handler(entry.getValue(), method, Pattern.compile(url));
                    // handlerMapping.put(url, method);
                    // 计算方法的参数位置信息
                    // query（HttpServletRequest req,HttpServletResponse resp,String name）
                    // 获取handler中的方法
                    Parameter[] parameters = method.getParameters();
                    for (int i = 0; i < parameters.length; i++) {
                        Parameter parameter = parameters[i];
                        if (parameter.getType() == HttpServletRequest.class || parameter.getType() == HttpServletResponse.class) {
                            // 如果参数是request和response对象，那么参数名称分别为 HttpServletRequest 和 HttpServletResponse
                            handler.getParamIndexMapping().put(parameter.getType().getSimpleName(), i);
                        } else {
                            handler.getParamIndexMapping().put(parameter.getName(), i);
                        }
                    }

                    // 处理用户权限
                    List<String> userListInMethod = new ArrayList<>(10);
                    if (method.isAnnotationPresent(Security.class)) {
                        String[] value = method.getAnnotation(Security.class).value();
                        if (ArrayUtils.isNotEmpty(value)) {
                            userListInMethod.addAll(Arrays.asList(value));
                        }
                    }
                    // 如果handler方法上配置了@Security就以方法上的为准，否则以类上配置的为准
                    if (CollectionUtils.isNotEmpty(userListInMethod)) {
                        handler.setUserPermissions(userListInMethod);
                    } else {
                        handler.setUserPermissions(userListInClass);
                    }
                    handlerMapping.add(handler);
                }


            }
        }

    }

    /**
     * 实现依赖注入
     */
    private void doAutoWired() {
        if (MapUtils.isEmpty(iocMap)) {
            return;
        }

        // 遍历ioc容器，并对这些对象中的带有@ZyAutoWired注解的字段进行以来注入
        for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
            // 获取对象
            Object object = entry.getValue();
            // 获取该对象的字段信息
            Field[] declaredFields = object.getClass().getDeclaredFields();
            for (Field field : declaredFields) {
                // 如果字段上没有@ZyAutoWired，继续循环
                if (!field.isAnnotationPresent(ZyAutoWired.class)) {
                    continue;
                }
                // 获取@ZyAutoWired注解
                ZyAutoWired annotation = field.getAnnotation(ZyAutoWired.class);
                String beanName = annotation.value();
                // 判断注解上是否有value值
                if (StringUtils.isBlank(beanName)) {
                    // 没有value值，则接口注入，如IDemoService
                    beanName = field.getType().getName();
                }

                // 开启赋值
                field.setAccessible(true);
                try {
                    field.set(object, iocMap.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 初始化Bean对象（基于注解实现IoC容器）
     */
    private void doInstance() {
        if (CollectionUtils.isEmpty(classNames)) {
            return;
        }
        try {
            for (String className : classNames) {
                // 此处className为com.zhouyer.study.Controller.DemoController
                Class<?> aClass = Class.forName(className);
                // 分别区分@ZyController和@ZyService注解
                if (aClass.isAnnotationPresent(ZyController.class)) {
                    //本应该判断@ZyController是否有value，此处不做过多处理，直接使用类名首字母小写作为id，保存到ioc中
                    String lowerFirstSimpleName = lowerFirst(aClass.getSimpleName());
                    Object o = aClass.newInstance();
                    iocMap.put(lowerFirstSimpleName, o);
                } else if (aClass.isAnnotationPresent(ZyService.class)) {

                    // 此处需要判断@ZyService注解是否有value
                    String id = aClass.getAnnotation(ZyService.class).value();
                    // 如果没有指定id，就使用类名首字母小写作为id
                    if (StringUtils.isBlank(id)) {
                        id = lowerFirst(aClass.getSimpleName());
                    }
                    Object o = aClass.newInstance();
                    iocMap.put(id, o);
                } else {
                    // 没有就继续
                    continue;
                }

                //service层往往是有接口的，面向接口编程，此时还需要以接口名为id，保存一份对象到ioc中，便于后期根据接口类型注入
                Class<?>[] interfaces = aClass.getInterfaces();
                for (Class<?> clazz : interfaces) {
                    // 这里以接口的全限定类名作为id，将对象保存到ioc容器中
                    iocMap.put(clazz.getName(), aClass.newInstance());
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }

    private String lowerFirst(String simpleClassName) {
        char[] chars = simpleClassName.toCharArray();
        if (chars[0] >= 'A' && chars[0] <= 'Z') {
            chars[0] += 32;
        }
        return String.valueOf(chars);
    }

    /**
     * 扫描相关的类，扫描注解
     *
     * @param scanPackage 值类似com.zhouyer.study  这是package，需要对应到磁盘上的文件夹 com/zhouyer/study
     */
    private void doScan(String scanPackage) {
        // 值类似com.zhouyer.study  这是package，需要对应到磁盘上的文件夹 com/zhouyer/study
        String s = scanPackage.replaceAll("\\.", "/");
        // 磁盘中的绝对路径
        String scanPackagePath = this.getClass().getClassLoader().getResource("").getPath() + s;
        File packFile = new File(scanPackagePath);

        File[] files = packFile.listFiles();

        for (File file : files) {
            // 如果是文件夹
            if (file.isDirectory()) {
                // 继续递归,com.zhouyer.study.controller
                doScan(scanPackage + "." + file.getName());
            } else if (file.getName().endsWith(".class")) {
                // 取出class文件
                String className = scanPackage + "." + file.getName().replaceAll(".class", "");
                classNames.add(className);
            }
        }
    }

    /**
     * 加载配置文件 springmvc.properties,并存入properties中
     *
     * @param scanPackage
     */
    private void doLoadConfig(String scanPackage) {
        //
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(scanPackage);
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 处理请求:根据url找到对应handler方法，即找到哪个controller对象中的哪个方法，进行调用
        // 获取uri
        // String requestUrl = req.getRequestURI();
        // Method method = (Method) handlerMapping.get(requestUrl);
        // 通过反射调用方法，需要传入对象，需要传入参数，此处无法完成调用，因为这里没有把对象缓存起来，也没有参数，只有改造initHandlerMapping，将对象和参数以及地址都存储下来
        //  method.invoke()
        Handler handler = getHandler(req);
        if (null == handler) {
            resp.getWriter().write("404 not found handler");
            return;
        }
        /***********   处理用户权限   *************/
        // 获取有权限的用户
        List<String> userList = handler.getUserPermissions();
        // 获取前端传入的用户：username
        String username = req.getParameter("username");
        // 如果没有权限，则直接返回无权限,不再往后执行
        if (!userList.contains(username)) {
            resp.setContentType("text/plain;charset=utf-8");
            resp.getWriter().write("该用户没有权限访问");
            return;
        }

        /***********   参数绑定   *************/
        Parameter[] parameters = handler.getMethod().getParameters();

        // 通过反射调用handler时，传入的参数
        Object[] paramValues = new Object[parameters.length];

        // 获取到request中传入的参数
        Map<String, String[]> parameterMap = req.getParameterMap();
        // 遍历request中的参数，除了request和response 之前的参数，因为request参数中没有这两个，这两个单独处理
        for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
            // String[] 表示有多个值，如name=1&name=2
            // 多个值有逗号隔开
            String value = StringUtils.join(param.getValue(), ",");
            // 如果request中的参数与handler方法中的参数不匹配，继续
            if (!handler.getParamIndexMapping().containsKey(param.getKey())) {
                continue;
            }
            // request中的参数找到在handler方法中形参的位置，对应把参数值放入到paramValue中
            Integer integer = handler.getParamIndexMapping().get(param.getKey());
            // 把前台传递过来的参数值填充到对应位置上
            paramValues[integer] = value;
        }

        // 填充request和response
        Integer requestIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName());
        paramValues[requestIndex] = req;
        Integer responseIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName());
        paramValues[responseIndex] = resp;

        // 最后调用handler的method方法
        try {
            handler.getMethod().invoke(handler.getController(), paramValues);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }

    private Handler getHandler(HttpServletRequest req) {
        if (CollectionUtils.isEmpty(handlerMapping)) {
            return null;
        }
        String requestURI = req.getRequestURI();
        for (Handler handler : handlerMapping) {
            Matcher matcher = handler.getPattern().matcher(requestURI);
            if (!matcher.matches()) {
                continue;
            }
            return handler;
        }
        return null;
    }
}
